#include <os/fs.h>
#include <os/mm.h>
#include <os/string.h>
#include <sbi.h>
#include <pgtable.h>
#include <stdio.h>

superblock_t *spb;
d_entry_t *cur_dentry;
inode_t *cur_dentry_inode;

char dentry_way[32];
uint32_t dentry_pos;

void allocMemForFsInKer()
{
    spb = allocPage();
    cur_dentry = allocPage();
    cur_dentry_inode = allocPage();
}

int allocInode()
{
    uint32_t i = 0;
    uint8_t j = 1;
    uint32_t inode_num;
    uint64_t buffer_kva;

    /* load inode map from sd card */
    buffer_kva = allocPage();
    sbi_sd_read((uint32_t)kva2pa(buffer_kva), SECTORS_PER_BLOCK, INODEMAP_START_SECTOR);

    /* look up for a free inode */
    while(*((uint8_t*)(buffer_kva + i)) == 0xff)
        i++;

    /* add inode map bit */
    inode_num = i * 8;
    while((*((uint8_t*)(buffer_kva + i)) & j) != 0)
    {
        j = j << 1;
        inode_num++;
    }
    *((uint8_t*)(buffer_kva + i)) += j; 

    /* renew inode map */
    sbi_sd_write((uint32_t)kva2pa(buffer_kva), SECTORS_PER_BLOCK, INODEMAP_START_SECTOR);
    freePage(buffer_kva);

    /* renew superblcok */
    spb->inodes_used_cnt += 1;
    sbi_sd_write((uint32_t)kva2pa(spb), 1, SUPERBLOCK_START_SECTOR);

    return inode_num;
}

void freeInode(uint32_t inode_num)
{
    uint32_t i = 0;
    uint64_t buffer_kva;
    inode_t *inode_ptr;

    /* clear inode */
    inode_ptr = allocPage();
    kmemset(inode_ptr, 0, 64);
    write_inode(inode_num, inode_ptr);
    freePage(inode_ptr);

    /* load inode map from sd card */
    buffer_kva = allocPage();
    sbi_sd_read((uint32_t)kva2pa(buffer_kva), SECTORS_PER_BLOCK, INODEMAP_START_SECTOR);

    /* delete inode map bit */
    *((uint8_t*)(buffer_kva + inode_num / 8)) -= (uint8_t)(1 << (inode_num % 8));

    /* renew inode map */
    sbi_sd_write((uint32_t)kva2pa(buffer_kva), SECTORS_PER_BLOCK, INODEMAP_START_SECTOR);
    freePage(buffer_kva);

    /* renew superblcok */
    spb->inodes_used_cnt -= 1;
    sbi_sd_write((uint32_t)kva2pa(spb), 1, SUPERBLOCK_START_SECTOR);
}

int allocBlock()
{
    uint32_t i = 0;
    uint32_t j = 0;
    uint8_t k = 1;
    uint32_t block_id;
    uint64_t buffer_kva;

    /* look up for a free block */
    buffer_kva = allocPage();
    sbi_sd_read((uint32_t)kva2pa(buffer_kva), SECTORS_PER_BLOCK, BLOCKMAP_START_SECTOR);
    while(1)
    {
        while(*((uint8_t*)(buffer_kva + i)) == 0xff && i < 0x1000)
            i++;
        if(i == 0x1000)
        {
            i = 0;
            j += 1;
            sbi_sd_read((uint32_t)kva2pa(buffer_kva), SECTORS_PER_BLOCK, j * SECTORS_PER_BLOCK + BLOCKMAP_START_SECTOR);
        }
        else
            break;
    }

    /* add block map bit */
    block_id = j * 0x8000 + i * 8;
    while((*((uint8_t*)(buffer_kva + i)) & k) != 0)
    {
        k = k << 1;
        block_id++;
    }
    *((uint8_t*)(buffer_kva + i)) += k;

    /* renew block map */
    sbi_sd_write((uint32_t)kva2pa(buffer_kva), SECTORS_PER_BLOCK, j * SECTORS_PER_BLOCK + BLOCKMAP_START_SECTOR);
    freePage(buffer_kva);

    /* renew superblcok */
    spb->blocks_used_cnt += 1;
    sbi_sd_write((uint32_t)kva2pa(spb), 1, SUPERBLOCK_START_SECTOR);
    
    return block_id;
}

void freeBlock(uint32_t block_id)
{
    uint64_t buffer_kva;
    uint32_t sector_id;
    uint32_t sector_offset;
    sector_id = block_id / 0x1000;
    sector_offset = block_id % 0x1000;

    /* clear block */
    buffer_kva = allocPage();
    sbi_sd_write((uint32_t)kva2pa(buffer_kva), SECTORS_PER_BLOCK, block_id * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);

    /* load block map form sd card */
    sbi_sd_read((uint32_t)kva2pa(buffer_kva), 1, BLOCKMAP_START_SECTOR + sector_id);

    /* delete block map */
    *((uint8_t*)(buffer_kva + sector_offset / 8)) -= (uint8_t)(1 << (sector_offset % 8));

    /* renew inode map */
    sbi_sd_write((uint32_t)kva2pa(buffer_kva), 1, BLOCKMAP_START_SECTOR + sector_id);
    freePage(buffer_kva);

    /* renew superblcok */
    spb->blocks_used_cnt -= 1;
    sbi_sd_write((uint32_t)kva2pa(spb), 1, SUPERBLOCK_START_SECTOR);
}

void read_inode(uint32_t inode_num, inode_t *inode)
{
    uint32_t inode_sector_offset;
    uint32_t inode_pos_inAsector;
    inode_t *inodesInAsector;

    /* find inode according to inode num */
    inodesInAsector = allocPage();
    inode_sector_offset = inode_num / 8;
    sbi_sd_read((uint32_t)kva2pa(inodesInAsector), 1, INODE_START_SECTOR + inode_sector_offset);
    inode_pos_inAsector = inode_num % 8;

    /* load inode */
    *inode = inodesInAsector[inode_pos_inAsector];
    freePage(inodesInAsector);
}

void write_inode(uint32_t inode_num, inode_t *inode)
{
    uint32_t inode_sector_offset;
    uint32_t inode_pos_inAsector;
    inode_t *inodesInAsector;

    /* find inode according to inode num */
    inodesInAsector = allocPage();
    inode_sector_offset = inode_num / 8;
    sbi_sd_read((uint32_t)kva2pa(inodesInAsector), 1, INODE_START_SECTOR + inode_sector_offset);
    inode_pos_inAsector = inode_num % 8;

    /* write back to sd card */
    inodesInAsector[inode_pos_inAsector] = *inode;
    sbi_sd_write((uint32_t)kva2pa(inodesInAsector), 1, INODE_START_SECTOR + inode_sector_offset);
    freePage(inodesInAsector);

}

void delete_dentry(uint32_t inode_num)
{
    inode_t *inode_ptr;

    inode_ptr = allocPage();
    read_inode(inode_num, inode_ptr);
    freeBlock(inode_ptr->direct[0]);
    freeInode(inode_num);
    freePage(inode_ptr);
}

void make_root()
{
    uint32_t inode_num;
    uint32_t block_id;

    inode_num = allocInode();
    block_id = allocBlock();

    /* create inode */
    cur_dentry_inode->inode_num = inode_num;
    cur_dentry_inode->type = TYPE_DENTRY;
    cur_dentry_inode->link = 0;
    cur_dentry_inode->direct[0] = block_id;
    write_inode(inode_num, cur_dentry_inode);

    /* create root data */
    cur_dentry->content[0].type = TYPE_DENTRY;
    kstrcpy(cur_dentry->content[0].name, ".");
    cur_dentry->content[0].inode_idx = inode_num;
    cur_dentry->content[1].type = TYPE_DENTRY;
    kstrcpy(cur_dentry->content[1].name, "..");
    cur_dentry->content[1].inode_idx = inode_num;
    sbi_sd_write((uint32_t)kva2pa(cur_dentry), SECTORS_PER_BLOCK, block_id * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);

    /* renew super block */
    spb->root_inode_num = inode_num;
    spb->root_blockid = block_id;
    sbi_sd_write((uint32_t)kva2pa(spb), 1, SUPERBLOCK_START_SECTOR);
}

/* return inode num according to its way */
int find_inode(char *way)
{
    uint32_t pos, l, k;
    uint32_t inode_num;
    char buffer[32];
    inode_t *cur_find_inode;
    d_entry_t *cur_find_dentry;
    pos = 0;
    k = 0;
    l = kstrlen(way);

    /* standardize input */
    way[l++] = '/';
    way[l] = '\0';

    cur_find_inode = allocPage();
    *cur_find_inode = *cur_dentry_inode;
    cur_find_dentry = allocPage();
    *cur_find_dentry = *cur_dentry;

    while(k < l)
    {
        if(way[k] == '/')
        {
            /* root dentry */
            if(pos == 0)
            {
                read_inode(spb->root_inode_num, cur_find_inode);
                sbi_sd_read((uint32_t)kva2pa(cur_find_dentry), SECTORS_PER_BLOCK, spb->root_blockid * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);
            }
            /* find dentry according to name */
            else
            {
                uint32_t i = 0;
                buffer[pos] = '\0';
                while(kstrcmp(cur_find_dentry->content[i].name, buffer) != 0)
                    i++;
                read_inode(cur_find_dentry->content[i].inode_idx, cur_find_inode);
                sbi_sd_read((uint32_t)kva2pa(cur_find_dentry), SECTORS_PER_BLOCK, cur_find_inode->direct[0] * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);
            }
            pos = 0;
        }
        else
        {
            buffer[pos] = way[k];
            pos++;
        }
        k++;
    }

    /* return current find inode num */
    inode_num = cur_find_inode->inode_num;
    freePage(cur_find_inode);
    freePage(cur_find_dentry);

    return inode_num;
}

int do_mkfs()
{
    int status;     //0: already exist a fs; 1: successfully creat fs
    sbi_sd_read((uint32_t)kva2pa(spb), 1, SUPERBLOCK_START_SECTOR);
    if(spb->magic != MAGIC)
    {
        /* create a superblock in memory and sd card */
        spb->magic = MAGIC;
        spb->fs_block_num = 0x40000;
        spb->start_block_id = SUPERBLOCK_START_BLOCK;
        spb->inodes_cnt = 0x2000;
        spb->data_blocks_cnt = 0x3ff00;
        spb->inodes_used_cnt = 0;
        spb->blocks_used_cnt = 0x100;
        spb->blockmap_block_offset = BLOCKMAP_START_BLOCK;
        spb->inodemap_block_offset = INODEMAP_START_BLOCK;
        spb->inode_block_offset = INODE_START_BLOCK;
        spb->data_block_offset = DATA_START_BLOCK;
        spb->inode_size = 0x40;
        spb->dentry_size = 0x1000;
        sbi_sd_write((uint32_t)kva2pa(spb), 1, SUPERBLOCK_START_SECTOR);

        /* initialize inode map */
        uint64_t buffer_kva = allocPage();
        sbi_sd_write((uint32_t)kva2pa(buffer_kva), SECTORS_PER_BLOCK, INODEMAP_START_SECTOR);
        freePage(buffer_kva);

        /* initialize block map */
        buffer_kva = allocPage();
        for(uint32_t i = 1; i < 8; i++)
            sbi_sd_write((uint32_t)kva2pa(buffer_kva), SECTORS_PER_BLOCK, BLOCKMAP_START_SECTOR + i * SECTORS_PER_BLOCK);
        for(uint32_t i = 0; i < 0x20; i++)
            *((uint8_t*)(buffer_kva + i)) = 0xff;
        sbi_sd_write((uint32_t)kva2pa(buffer_kva), SECTORS_PER_BLOCK, BLOCKMAP_START_SECTOR);
        freePage(buffer_kva);

        /* create root dentry */
        make_root();

        status = 1;
    }else{
        /* read root dentry and its inode */
        read_inode(spb->root_inode_num, cur_dentry_inode);
        sbi_sd_read((uint32_t)kva2pa(cur_dentry), SECTORS_PER_BLOCK, spb->root_blockid * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);

        status = 0;
    }

    /* initialize dentry way */
    dentry_way[0] = '/';
    dentry_way[1] = '\0';
    dentry_pos = 1;
    
    return status;
}

int do_mkdir(char *name)        //0: already exist this dentry; 1: successfully create
{
    uint32_t i = 0;
    
    /* find if name is used */
    while(i < 127)
    {
        if(!kstrcmp(cur_dentry->content[i].name, name))
            return 0;
        i++;
    }

    /* find a unused pos */
    i = 0;
    while(cur_dentry->content[i].type != 0)
        i++;

    /* create dentry */
    uint32_t inode_num = allocInode();
    uint32_t block_id = allocBlock();
    d_entry_t *dentry_ptr;
    inode_t *dentry_inode_ptr;
    dentry_ptr = allocPage();
    dentry_inode_ptr = allocPage();

    /* create dentry content ".", ".." */
    dentry_ptr->content[0].type = TYPE_DENTRY;
    kstrcpy(dentry_ptr->content[0].name, ".");
    dentry_ptr->content[0].inode_idx = inode_num;

    dentry_ptr->content[1].type = TYPE_DENTRY;
    kstrcpy(dentry_ptr->content[1].name, "..");
    dentry_ptr->content[1].inode_idx = cur_dentry_inode->inode_num;

    /* write back dentry */
    sbi_sd_write((uint32_t)kva2pa(dentry_ptr), SECTORS_PER_BLOCK, block_id * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);
    freePage(dentry_ptr);

    /* create dentry inode */
    dentry_inode_ptr->inode_num = inode_num;
    dentry_inode_ptr->ref_num = 0;
    dentry_inode_ptr->type = TYPE_DENTRY;
    dentry_inode_ptr->link = 0;
    dentry_inode_ptr->direct[0] = block_id;
    
    /* write back dentry inode */
    write_inode(inode_num, dentry_inode_ptr);
    freePage(dentry_inode_ptr);

    /* renew father dentry */
    cur_dentry->content[i].type = TYPE_DENTRY;
    kstrcpy(cur_dentry->content[i].name, name);
    cur_dentry->content[i].inode_idx = inode_num;
    sbi_sd_write((uint32_t)kva2pa(cur_dentry), SECTORS_PER_BLOCK, cur_dentry_inode->direct[0] * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);

    return 1;
}

int do_rmdir(char *name)
{
    /* find the dentry */
    uint32_t i = 2;
    while(i < 127)
    {
        if(!kstrcmp(cur_dentry->content[i].name, name))
            break;
        i++;
    }

    /* check if the file is dentry */
    if(cur_dentry->content[i].type == TYPE_DENTRY)
    {
        /* delete inode of dentry */
        delete_dentry(cur_dentry->content[i].inode_idx);

        /* renew father dentry */
        kmemset(&cur_dentry->content[i], 0, 32);
        sbi_sd_write((uint32_t)kva2pa(cur_dentry_inode), SECTORS_PER_BLOCK, cur_dentry_inode->direct[0] * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);
    }
    else
        return 0;

    return 1;
}

int do_cd(char *way)
{
    uint32_t inode_num;
    inode_num = find_inode(way);

    /* judge if dentry */
    inode_t *inode_ptr;
    inode_ptr = allocPage();
    read_inode(inode_num, inode_ptr);
    if(inode_ptr->type == TYPE_DENTRY)
    {
        /* renew current dentry */
        read_inode(inode_num, cur_dentry_inode);
        sbi_sd_read((uint32_t)kva2pa(cur_dentry), SECTORS_PER_BLOCK, cur_dentry_inode->direct[0] * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);
    }

    freePage(inode_ptr);

    return 1;
}

void do_statfs()
{
    prints("magic: 0x%x\n", spb->magic);
    prints("size: 1GB, block size: 4KB\n");
    prints("inode map offset: 0x%x, inode map size:1KB, used: 0x%x/0x%x\n", spb->inodemap_block_offset, spb->inodes_used_cnt, spb->inodes_cnt);
    prints("block map offset: 0x%x, block map size:32KB, used: 0x%x/0x%x\n", spb->blockmap_block_offset, spb->blocks_used_cnt, spb->fs_block_num);
    prints("inode offset: 0x%x, inode size: 0x%xB\n", spb->inode_block_offset, spb->inode_size);
    prints("data offset: 0x%x, ocuppied blocks: 0x%x\n", spb->data_block_offset, spb->data_blocks_cnt);
    prints("dentry size: 0x%x\n", spb->dentry_size);
}

void do_show_files()
{
    uint32_t i = 2;
    while(i < 127){
        if(cur_dentry->content[i].type != 0){
            inode_t *inode_ptr;
            inode_ptr = allocPage();
            read_inode(cur_dentry->content[i].inode_idx, inode_ptr);
            prints("%s(%d) ", cur_dentry->content[i].name, inode_ptr->ref_num);
            freePage(inode_ptr);
        }
        i++;
    }
    prints("\n");
}